#include "board_ex.h"

#include "hpm_gpio_drv.h"
#include "hpm_tsns_drv.h"
#include "hpm_otp_drv.h"
#include "hpm_ppor_drv.h"
#include "hpm_gptmr_drv.h"

#include "FreeRTOS.h"
#include "task.h"

/* Macro Definition - TSNS */
#define OTP_SHADOW_TSNS (21U)
#define OTP_SHADOW_TSNS_BASE_MASK ((1 << 12) - 1)
#define OTP_SHADOW_TSNS_BASE_EN_MASK (1 << 24)
#define OTP_TSNS_25C_BASE (0x956U)
#define OTP_TSNS_RATIO (0x870 << 12)

/* Global variables */
volatile uint32_t g_freertos_run_time_counter = 0;
volatile uint64_t g_up_time_100us_counter = 0;

/* Static Variable Definition*/
static struct device_info s_device_info = { 
    .device_name = "HPM5E-EC-DEV", 
    .firmware_version = (1 << 16) | (0 << 8) | (0),
    .up_time = 0, 
    .cpu_usage = 0,
    .memory_usage = 0,
    .temperature = 0,
};

static void calibrate_tsns(void)
{
    volatile float temp;
    uint32_t i = 0;
    i = otp_read_from_shadow(OTP_SHADOW_TSNS) & OTP_SHADOW_TSNS_BASE_MASK;

    if (i) {
        /* assume calibrated already */
        return;
    }

    i = OTP_TSNS_25C_BASE;
    printf("before calibration: temp %.1f, read from shadow 0x%x\n", tsns_get_current_temp(HPM_TSNS), i);
    printf("calibration starts\n");
    while (1) {
        printf(".");
        temp = tsns_get_current_temp(HPM_TSNS);
        if (temp > 25.1) {
            printf("%.1f too high: ", temp);
            i -= 1;
        } else if (temp < 24.9) {
            printf("%.1f too low: ", temp);
            i += 1;
        } else {
            break;
        }
        if (status_success != otp_write_shadow_register(OTP_SHADOW_TSNS, OTP_SHADOW_TSNS_BASE_EN_MASK
                                                                       | OTP_TSNS_RATIO
                                                                       | (i & OTP_SHADOW_TSNS_BASE_MASK))) {
            while (1) {
                ;
            }
        }
        printf("set new offset: 0x%X\n", i);
        board_delay_ms(1000);
    }
    printf("\ncalibration done\n");
    printf("after calibration: temp %.1f, read from shadow 0x%x\n", tsns_get_current_temp(HPM_TSNS), otp_read_from_shadow(OTP_SHADOW_TSNS));
}

static void on_timer(void)
{
  g_freertos_run_time_counter++;
  g_up_time_100us_counter++;
}

static float get_cpu_usage_percent(void)
{
    TaskStatus_t *pxTaskStatusArray;
    uint32_t      ulTotalRunTime;
    uint32_t      ulIdleTime = 0;
    UBaseType_t   uxArraySize, uxTask;

    uxArraySize = uxTaskGetNumberOfTasks();
    pxTaskStatusArray = (TaskStatus_t *)pvPortMalloc(uxArraySize * sizeof(TaskStatus_t));
    if (pxTaskStatusArray == NULL) return 0;

    uxArraySize = uxTaskGetSystemState(pxTaskStatusArray,
                                       uxArraySize,
                                       &ulTotalRunTime); 

    if (ulTotalRunTime > 0)
    {
      for (uxTask = 0; uxTask < uxArraySize; uxTask++) {
          if (pxTaskStatusArray[uxTask].xHandle == xTaskGetIdleTaskHandle()) {
              ulIdleTime = pxTaskStatusArray[uxTask].ulRunTimeCounter;
              break;
          }
      }

      vPortFree(pxTaskStatusArray);

      return 100.0f - (ulIdleTime * 100.0f / ulTotalRunTime);
    }
    else
    {
      return 0;
    }
}

void board_init2(void)
{
    /* init tsns */
    clock_add_to_group(clock_tsns, 0);
    tsns_enable_continuous_mode(HPM_TSNS);
    tsns_enable(HPM_TSNS);
    calibrate_tsns();

    /* init gptmr0, buzzer */
    clock_add_to_group(clock_gptmr0, 0);

    /* init gptmr3, 10KHz */
    board_timer_create_us(100, on_timer);
}

void board_get_device_info(struct device_info* data)
{
    s_device_info.up_time = g_up_time_100us_counter / 10 / 1000;

    s_device_info.cpu_usage = get_cpu_usage_percent();

    size_t xFreeHeapSize = xPortGetFreeHeapSize(); 
    size_t xTotalHeapSize = configTOTAL_HEAP_SIZE; 
    float fHeapUsagePercentage = 100.0f * (1.0f - ((float)xFreeHeapSize / (float)xTotalHeapSize));  
    s_device_info.memory_usage = fHeapUsagePercentage;

    s_device_info.temperature = tsns_get_current_temp(HPM_TSNS);

    *data = s_device_info;
}

void board_reboot(void)
{
    ppor_reset_mask_set_source_enable(HPM_PPOR, ppor_reset_software);
    /* reset after 1us */
    ppor_sw_reset(HPM_PPOR, 24);
    while(1) {
    }
}

void board_get_led(struct led *data)
{
    if (data->number == 1)
      data->state = gpio_read_pin(BOARD_APP_LED1_CTRL, BOARD_APP_LED1_INDEX, BOARD_APP_LED1_PIN) != 0 ? true : false;
    else if (data->number == 2)
      data->state = gpio_read_pin(BOARD_APP_LED2_CTRL, BOARD_APP_LED2_INDEX, BOARD_APP_LED2_PIN) != 0 ? true : false;
}

void board_set_led(struct led *data)
{
    if (data->number == 1)
      gpio_write_pin(BOARD_APP_LED1_CTRL, BOARD_APP_LED1_INDEX, BOARD_APP_LED1_PIN, data->state ? 1 : 0);
    else if (data->number == 2)
      gpio_write_pin(BOARD_APP_LED2_CTRL, BOARD_APP_LED2_INDEX, BOARD_APP_LED2_PIN, data->state ? 1 : 0);
}

static struct buzzer s_buzzer;

void board_get_buzzer(struct buzzer *data)
{
  data->frequency = s_buzzer.frequency;
  data->is_running = s_buzzer.is_running;
}

void board_set_buzzer(struct buzzer *data)
{
  s_buzzer.frequency = data->frequency;
  s_buzzer.is_running = data->is_running;

  if (!s_buzzer.is_running || s_buzzer.frequency == 0)
  {
    gptmr_update_cmp(HPM_GPTMR0, 3, 0, 0);
    gptmr_stop_counter(HPM_GPTMR0, 3);
  }
  else
  {
    gptmr_channel_config_t gptmr_config;
    uint32_t gptmr_freq;
    gptmr_channel_get_default_config(HPM_GPTMR0, &gptmr_config);
    gptmr_freq = clock_get_frequency(clock_gptmr0);              /* 获取GPTMR0的时钟频率 */
    gptmr_config.mode = gptmr_work_mode_no_capture;              /* 无捕获 */
    gptmr_config.reload = gptmr_freq / s_buzzer.frequency;       /* 频率 */
    gptmr_config.cmp[0] = gptmr_config.reload * 0.5;             /* 占空比 */
    gptmr_config.enable_cmp_output = true;                       /* 使能输出比较 */
    gptmr_config.enable_software_sync = false;                   /* 不使能软件同步 */
    gptmr_channel_config(HPM_GPTMR0, 3, &gptmr_config, true);
    gptmr_channel_reset_count(HPM_GPTMR0, 3);
    gptmr_start_counter(HPM_GPTMR0, 3);
  }
}
